// Copyright 2016 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef V8_API_ARGUMENTS_INL_H_
#define V8_API_ARGUMENTS_INL_H_

#include "src/api-arguments.h"

#include "src/api-inl.h"
#include "src/debug/debug.h"
#include "src/objects/api-callbacks.h"
#include "src/objects/slots-inl.h"
#include "src/tracing/trace-event.h"
#include "src/vm-state-inl.h"

namespace v8 {
namespace internal {

    void Object::VerifyApiCallResultType()
    {
#if DEBUG
        if (IsSmi())
            return;
        DCHECK(IsHeapObject());
        if (!(IsString() || IsSymbol() || IsJSReceiver() || IsHeapNumber() || IsBigInt() || IsUndefined() || IsTrue() || IsFalse() || IsNull())) {
            FATAL("API call returned invalid object");
        }
#endif // DEBUG
    }

    CustomArgumentsBase::CustomArgumentsBase(Isolate* isolate)
        : Relocatable(isolate)
    {
    }

    template <typename T>
    CustomArguments<T>::~CustomArguments()
    {
        slot_at(kReturnValueOffset).store(Object(kHandleZapValue));
    }

    template <typename T>
    template <typename V>
    Handle<V> CustomArguments<T>::GetReturnValue(Isolate* isolate)
    {
        // Check the ReturnValue.
        FullObjectSlot slot = slot_at(kReturnValueOffset);
        // Nothing was set, return empty handle as per previous behaviour.
        if ((*slot)->IsTheHole(isolate))
            return Handle<V>();
        Handle<V> result = Handle<V>::cast(Handle<Object>(slot.location()));
        result->VerifyApiCallResultType();
        return result;
    }

    inline JSObject PropertyCallbackArguments::holder()
    {
        return JSObject::cast(*slot_at(T::kHolderIndex));
    }

    inline Object PropertyCallbackArguments::receiver()
    {
        return *slot_at(T::kThisIndex);
    }

    inline JSObject FunctionCallbackArguments::holder()
    {
        return JSObject::cast(*slot_at(T::kHolderIndex));
    }

#define FOR_EACH_CALLBACK(F)                          \
    F(Query, query, Object, v8::Integer, interceptor) \
    F(Deleter, deleter, Object, v8::Boolean, Handle<Object>())

#define DCHECK_NAME_COMPATIBLE(interceptor, name) \
    DCHECK(interceptor->is_named());              \
    DCHECK(!name->IsPrivate());                   \
    DCHECK_IMPLIES(name->IsSymbol(), interceptor->can_intercept_symbols());

#define PREPARE_CALLBACK_INFO(ISOLATE, F, RETURN_VALUE, API_RETURN_TYPE,                                                                                                        \
    CALLBACK_INFO, RECEIVER, ACCESSOR_KIND)                                                                                                                                     \
    if (ISOLATE->debug_execution_mode() == DebugInfo::kSideEffects && !ISOLATE->debug()->PerformSideEffectCheckForCallback(CALLBACK_INFO, RECEIVER, Debug::k##ACCESSOR_KIND)) { \
        return RETURN_VALUE();                                                                                                                                                  \
    }                                                                                                                                                                           \
    VMState<EXTERNAL> state(ISOLATE);                                                                                                                                           \
    ExternalCallbackScope call_scope(ISOLATE, FUNCTION_ADDR(F));                                                                                                                \
    PropertyCallbackInfo<API_RETURN_TYPE> callback_info(values_);

#define PREPARE_CALLBACK_INFO_FAIL_SIDE_EFFECT_CHECK(ISOLATE, F, RETURN_VALUE, \
    API_RETURN_TYPE)                                                           \
    if (ISOLATE->debug_execution_mode() == DebugInfo::kSideEffects) {          \
        return RETURN_VALUE();                                                 \
    }                                                                          \
    VMState<EXTERNAL> state(ISOLATE);                                          \
    ExternalCallbackScope call_scope(ISOLATE, FUNCTION_ADDR(F));               \
    PropertyCallbackInfo<API_RETURN_TYPE> callback_info(values_);

#define CREATE_NAMED_CALLBACK(FUNCTION, TYPE, RETURN_TYPE, API_RETURN_TYPE,                             \
    INFO_FOR_SIDE_EFFECT)                                                                               \
    Handle<RETURN_TYPE> PropertyCallbackArguments::CallNamed##FUNCTION(                                 \
        Handle<InterceptorInfo> interceptor, Handle<Name> name)                                         \
    {                                                                                                   \
        DCHECK_NAME_COMPATIBLE(interceptor, name);                                                      \
        Isolate* isolate = this->isolate();                                                             \
        RuntimeCallTimerScope timer(                                                                    \
            isolate, RuntimeCallCounterId::kNamed##FUNCTION##Callback);                                 \
        Handle<Object> receiver_check_unsupported;                                                      \
        GenericNamedProperty##FUNCTION##Callback f = ToCData<GenericNamedProperty##FUNCTION##Callback>( \
            interceptor->TYPE());                                                                       \
        PREPARE_CALLBACK_INFO(isolate, f, Handle<RETURN_TYPE>, API_RETURN_TYPE,                         \
            INFO_FOR_SIDE_EFFECT, receiver_check_unsupported,                                           \
            NotAccessor);                                                                               \
        LOG(isolate,                                                                                    \
            ApiNamedPropertyAccess("interceptor-named-" #TYPE, holder(), *name));                       \
        f(v8::Utils::ToLocal(name), callback_info);                                                     \
        return GetReturnValue<RETURN_TYPE>(isolate);                                                    \
    }

    FOR_EACH_CALLBACK(CREATE_NAMED_CALLBACK)
#undef CREATE_NAMED_CALLBACK

#define CREATE_INDEXED_CALLBACK(FUNCTION, TYPE, RETURN_TYPE, API_RETURN_TYPE,                                      \
    INFO_FOR_SIDE_EFFECT)                                                                                          \
    Handle<RETURN_TYPE> PropertyCallbackArguments::CallIndexed##FUNCTION(                                          \
        Handle<InterceptorInfo> interceptor, uint32_t index)                                                       \
    {                                                                                                              \
        DCHECK(!interceptor->is_named());                                                                          \
        Isolate* isolate = this->isolate();                                                                        \
        RuntimeCallTimerScope timer(                                                                               \
            isolate, RuntimeCallCounterId::kIndexed##FUNCTION##Callback);                                          \
        Handle<Object> receiver_check_unsupported;                                                                 \
        IndexedProperty##FUNCTION##Callback f = ToCData<IndexedProperty##FUNCTION##Callback>(interceptor->TYPE()); \
        PREPARE_CALLBACK_INFO(isolate, f, Handle<RETURN_TYPE>, API_RETURN_TYPE,                                    \
            INFO_FOR_SIDE_EFFECT, receiver_check_unsupported,                                                      \
            NotAccessor);                                                                                          \
        LOG(isolate, ApiIndexedPropertyAccess("interceptor-indexed-" #TYPE, holder(), index));                     \
        f(index, callback_info);                                                                                   \
        return GetReturnValue<RETURN_TYPE>(isolate);                                                               \
    }

    FOR_EACH_CALLBACK(CREATE_INDEXED_CALLBACK)

#undef FOR_EACH_CALLBACK
#undef CREATE_INDEXED_CALLBACK

    Handle<Object> FunctionCallbackArguments::Call(CallHandlerInfo handler)
    {
        Isolate* isolate = this->isolate();
        LOG(isolate, ApiObjectAccess("call", holder()));
        RuntimeCallTimerScope timer(isolate, RuntimeCallCounterId::kFunctionCallback);
        v8::FunctionCallback f = v8::ToCData<v8::FunctionCallback>(handler->callback());
        Handle<Object> receiver_check_unsupported;
        if (isolate->debug_execution_mode() == DebugInfo::kSideEffects && !isolate->debug()->PerformSideEffectCheckForCallback(handle(handler, isolate), receiver_check_unsupported, Debug::kNotAccessor)) {
            return Handle<Object>();
        }
        VMState<EXTERNAL> state(isolate);
        ExternalCallbackScope call_scope(isolate, FUNCTION_ADDR(f));
        FunctionCallbackInfo<v8::Value> info(values_, argv_, argc_);
        f(info);
        return GetReturnValue<Object>(isolate);
    }

    Handle<JSObject> PropertyCallbackArguments::CallNamedEnumerator(
        Handle<InterceptorInfo> interceptor)
    {
        DCHECK(interceptor->is_named());
        LOG(isolate(), ApiObjectAccess("interceptor-named-enumerator", holder()));
        RuntimeCallTimerScope timer(isolate(),
            RuntimeCallCounterId::kNamedEnumeratorCallback);
        return CallPropertyEnumerator(interceptor);
    }

    Handle<JSObject> PropertyCallbackArguments::CallIndexedEnumerator(
        Handle<InterceptorInfo> interceptor)
    {
        DCHECK(!interceptor->is_named());
        LOG(isolate(), ApiObjectAccess("interceptor-indexed-enumerator", holder()));
        RuntimeCallTimerScope timer(isolate(),
            RuntimeCallCounterId::kIndexedEnumeratorCallback);
        return CallPropertyEnumerator(interceptor);
    }

    Handle<Object> PropertyCallbackArguments::CallNamedGetter(
        Handle<InterceptorInfo> interceptor, Handle<Name> name)
    {
        DCHECK_NAME_COMPATIBLE(interceptor, name);
        Isolate* isolate = this->isolate();
        RuntimeCallTimerScope timer(isolate,
            RuntimeCallCounterId::kNamedGetterCallback);
        LOG(isolate,
            ApiNamedPropertyAccess("interceptor-named-getter", holder(), *name));
        GenericNamedPropertyGetterCallback f = ToCData<GenericNamedPropertyGetterCallback>(interceptor->getter());
        return BasicCallNamedGetterCallback(f, name, interceptor);
    }

    Handle<Object> PropertyCallbackArguments::CallNamedDescriptor(
        Handle<InterceptorInfo> interceptor, Handle<Name> name)
    {
        DCHECK_NAME_COMPATIBLE(interceptor, name);
        Isolate* isolate = this->isolate();
        RuntimeCallTimerScope timer(isolate,
            RuntimeCallCounterId::kNamedDescriptorCallback);
        LOG(isolate,
            ApiNamedPropertyAccess("interceptor-named-descriptor", holder(), *name));
        GenericNamedPropertyDescriptorCallback f = ToCData<GenericNamedPropertyDescriptorCallback>(
            interceptor->descriptor());
        return BasicCallNamedGetterCallback(f, name, interceptor);
    }

    Handle<Object> PropertyCallbackArguments::BasicCallNamedGetterCallback(
        GenericNamedPropertyGetterCallback f, Handle<Name> name,
        Handle<Object> info, Handle<Object> receiver)
    {
        DCHECK(!name->IsPrivate());
        Isolate* isolate = this->isolate();
        PREPARE_CALLBACK_INFO(isolate, f, Handle<Object>, v8::Value, info, receiver,
            Getter);
        f(v8::Utils::ToLocal(name), callback_info);
        return GetReturnValue<Object>(isolate);
    }

    Handle<Object> PropertyCallbackArguments::CallNamedSetter(
        Handle<InterceptorInfo> interceptor, Handle<Name> name,
        Handle<Object> value)
    {
        DCHECK_NAME_COMPATIBLE(interceptor, name);
        GenericNamedPropertySetterCallback f = ToCData<GenericNamedPropertySetterCallback>(interceptor->setter());
        Isolate* isolate = this->isolate();
        RuntimeCallTimerScope timer(isolate,
            RuntimeCallCounterId::kNamedSetterCallback);
        PREPARE_CALLBACK_INFO_FAIL_SIDE_EFFECT_CHECK(isolate, f, Handle<Object>,
            v8::Value);
        LOG(isolate,
            ApiNamedPropertyAccess("interceptor-named-set", holder(), *name));
        f(v8::Utils::ToLocal(name), v8::Utils::ToLocal(value), callback_info);
        return GetReturnValue<Object>(isolate);
    }

    Handle<Object> PropertyCallbackArguments::CallNamedDefiner(
        Handle<InterceptorInfo> interceptor, Handle<Name> name,
        const v8::PropertyDescriptor& desc)
    {
        DCHECK_NAME_COMPATIBLE(interceptor, name);
        Isolate* isolate = this->isolate();
        RuntimeCallTimerScope timer(isolate,
            RuntimeCallCounterId::kNamedDefinerCallback);
        GenericNamedPropertyDefinerCallback f = ToCData<GenericNamedPropertyDefinerCallback>(interceptor->definer());
        PREPARE_CALLBACK_INFO_FAIL_SIDE_EFFECT_CHECK(isolate, f, Handle<Object>,
            v8::Value);
        LOG(isolate,
            ApiNamedPropertyAccess("interceptor-named-define", holder(), *name));
        f(v8::Utils::ToLocal(name), desc, callback_info);
        return GetReturnValue<Object>(isolate);
    }

    Handle<Object> PropertyCallbackArguments::CallIndexedSetter(
        Handle<InterceptorInfo> interceptor, uint32_t index, Handle<Object> value)
    {
        DCHECK(!interceptor->is_named());
        Isolate* isolate = this->isolate();
        RuntimeCallTimerScope timer(isolate,
            RuntimeCallCounterId::kIndexedSetterCallback);
        IndexedPropertySetterCallback f = ToCData<IndexedPropertySetterCallback>(interceptor->setter());
        PREPARE_CALLBACK_INFO_FAIL_SIDE_EFFECT_CHECK(isolate, f, Handle<Object>,
            v8::Value);
        LOG(isolate,
            ApiIndexedPropertyAccess("interceptor-indexed-set", holder(), index));
        f(index, v8::Utils::ToLocal(value), callback_info);
        return GetReturnValue<Object>(isolate);
    }

    Handle<Object> PropertyCallbackArguments::CallIndexedDefiner(
        Handle<InterceptorInfo> interceptor, uint32_t index,
        const v8::PropertyDescriptor& desc)
    {
        DCHECK(!interceptor->is_named());
        Isolate* isolate = this->isolate();
        RuntimeCallTimerScope timer(isolate,
            RuntimeCallCounterId::kIndexedDefinerCallback);
        IndexedPropertyDefinerCallback f = ToCData<IndexedPropertyDefinerCallback>(interceptor->definer());
        PREPARE_CALLBACK_INFO_FAIL_SIDE_EFFECT_CHECK(isolate, f, Handle<Object>,
            v8::Value);
        LOG(isolate,
            ApiIndexedPropertyAccess("interceptor-indexed-define", holder(), index));
        f(index, desc, callback_info);
        return GetReturnValue<Object>(isolate);
    }

    Handle<Object> PropertyCallbackArguments::CallIndexedGetter(
        Handle<InterceptorInfo> interceptor, uint32_t index)
    {
        DCHECK(!interceptor->is_named());
        Isolate* isolate = this->isolate();
        RuntimeCallTimerScope timer(isolate,
            RuntimeCallCounterId::kNamedGetterCallback);
        LOG(isolate,
            ApiIndexedPropertyAccess("interceptor-indexed-getter", holder(), index));
        IndexedPropertyGetterCallback f = ToCData<IndexedPropertyGetterCallback>(interceptor->getter());
        return BasicCallIndexedGetterCallback(f, index, interceptor);
    }

    Handle<Object> PropertyCallbackArguments::CallIndexedDescriptor(
        Handle<InterceptorInfo> interceptor, uint32_t index)
    {
        DCHECK(!interceptor->is_named());
        Isolate* isolate = this->isolate();
        RuntimeCallTimerScope timer(isolate,
            RuntimeCallCounterId::kIndexedDescriptorCallback);
        LOG(isolate, ApiIndexedPropertyAccess("interceptor-indexed-descriptor", holder(), index));
        IndexedPropertyDescriptorCallback f = ToCData<IndexedPropertyDescriptorCallback>(interceptor->descriptor());
        return BasicCallIndexedGetterCallback(f, index, interceptor);
    }

    Handle<Object> PropertyCallbackArguments::BasicCallIndexedGetterCallback(
        IndexedPropertyGetterCallback f, uint32_t index, Handle<Object> info)
    {
        Isolate* isolate = this->isolate();
        Handle<Object> receiver_check_unsupported;
        PREPARE_CALLBACK_INFO(isolate, f, Handle<Object>, v8::Value, info,
            receiver_check_unsupported, Getter);
        f(index, callback_info);
        return GetReturnValue<Object>(isolate);
    }

    Handle<JSObject> PropertyCallbackArguments::CallPropertyEnumerator(
        Handle<InterceptorInfo> interceptor)
    {
        // For now there is a single enumerator for indexed and named properties.
        IndexedPropertyEnumeratorCallback f = v8::ToCData<IndexedPropertyEnumeratorCallback>(interceptor->enumerator());
        // TODO(cbruni): assert same type for indexed and named callback.
        Isolate* isolate = this->isolate();
        Handle<Object> receiver_check_unsupported;
        PREPARE_CALLBACK_INFO(isolate, f, Handle<JSObject>, v8::Array, interceptor,
            receiver_check_unsupported, NotAccessor);
        f(callback_info);
        return GetReturnValue<JSObject>(isolate);
    }

    // -------------------------------------------------------------------------
    // Accessors

    Handle<Object> PropertyCallbackArguments::CallAccessorGetter(
        Handle<AccessorInfo> info, Handle<Name> name)
    {
        Isolate* isolate = this->isolate();
        RuntimeCallTimerScope timer(isolate,
            RuntimeCallCounterId::kAccessorGetterCallback);
        LOG(isolate, ApiNamedPropertyAccess("accessor-getter", holder(), *name));
        AccessorNameGetterCallback f = ToCData<AccessorNameGetterCallback>(info->getter());
        return BasicCallNamedGetterCallback(f, name, info,
            handle(receiver(), isolate));
    }

    Handle<Object> PropertyCallbackArguments::CallAccessorSetter(
        Handle<AccessorInfo> accessor_info, Handle<Name> name,
        Handle<Object> value)
    {
        Isolate* isolate = this->isolate();
        RuntimeCallTimerScope timer(isolate,
            RuntimeCallCounterId::kAccessorSetterCallback);
        AccessorNameSetterCallback f = ToCData<AccessorNameSetterCallback>(accessor_info->setter());
        PREPARE_CALLBACK_INFO(isolate, f, Handle<Object>, void, accessor_info,
            handle(receiver(), isolate), Setter);
        LOG(isolate, ApiNamedPropertyAccess("accessor-setter", holder(), *name));
        f(v8::Utils::ToLocal(name), v8::Utils::ToLocal(value), callback_info);
        return GetReturnValue<Object>(isolate);
    }

#undef PREPARE_CALLBACK_INFO
#undef PREPARE_CALLBACK_INFO_FAIL_SIDE_EFFECT_CHECK

} // namespace internal
} // namespace v8

#endif // V8_API_ARGUMENTS_INL_H_
